package com.xiaoxin.experience.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author xiaoxin
 */
public class ThreadUtil
{
    private static final Logger LOG = LoggerFactory.getLogger(ThreadUtil.class);

    private static final ThreadPoolTaskScheduler THREAD_POOL_TASK_SCHEDULER = new ThreadPoolTaskScheduler();

    static
    {
        THREAD_POOL_TASK_SCHEDULER.setPoolSize(2);
        THREAD_POOL_TASK_SCHEDULER.initialize();
        THREAD_POOL_TASK_SCHEDULER.schedule(ThreadUtil::intervalTaskEngine,new CronTrigger("0/1 * * * * ?"));
    }

    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR =
            new ThreadPoolExecutor(SystemUtil.cpuCore() + 1,
                    SystemUtil.cpuCore() + 1,
                    0L,TimeUnit.MILLISECONDS,
                    new ArrayBlockingQueue<Runnable>(1000),
                    new MyThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy());

    private static final List<TaskInfo> TASK_INFO_LIST = new ArrayList<>();

    public static void sleep(int seconds)
    {
        sleep(seconds,TimeUnit.SECONDS);
    }

    public static void sleep(int time, TimeUnit timeUnit)
    {
        try
        {
            timeUnit.sleep(time);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    public static ThreadPoolExecutor defaultThreadPool()
    {
        return THREAD_POOL_EXECUTOR;
    }

    public static ThreadPoolExecutor getFixThreadPoolExecutor(int size, String prefix)
    {
        String realPrefix;
        if (StringUtil.isBlank(prefix))
        {
            realPrefix = "xiaoxin";
        }
        else
        {
            realPrefix = prefix;
        }
        return new ThreadPoolExecutor(size,size,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(10000),
                new MyThreadFactory(realPrefix),
                new ThreadPoolExecutor.AbortPolicy());
    }

    public static void execute(Runnable runnable)
    {
        try
        {
            THREAD_POOL_EXECUTOR.execute(runnable);
        }
        catch (Exception e)
        {
            // 此处进行异常捕获避免线程因拒绝策略导致主线程中断
            LOG.error("task run error: ",e);
        }
    }

    public static <T> T executeTimeout(Callable<T> runnable,long timeout, TimeUnit timeUnit)
    {
        Future<T> task = THREAD_POOL_EXECUTOR.submit(runnable);
        try
        {
            return task.get(timeout,timeUnit);
        }
        catch (InterruptedException e)
        {
            task.cancel(true);
        }
        catch (TimeoutException | ExecutionException e)
        {
            LOG.error("task timeout!");
        }
        return null;
    }

    private static void intervalTaskEngine()
    {
        if (TASK_INFO_LIST.isEmpty())
        {
            return;
        }
        List<TaskInfo> remove = new ArrayList<>();
        List<TaskInfo> taskInfos = new ArrayList<>(TASK_INFO_LIST);
        for (TaskInfo taskInfo : taskInfos)
        {
            if (System.currentTimeMillis() - taskInfo.getCreatTime() > taskInfo.getDelay())
            {
                execute(taskInfo.getWork());
                remove.add(taskInfo);
            }
        }
        TASK_INFO_LIST.removeAll(remove);
    }

    public static void postDelay(Runnable run, long delay)
    {
        postDelay(run,delay,System.currentTimeMillis());
    }

    public static void postDelay(Runnable run, long delay, long creatTime)
    {
        TaskInfo taskInfo = new TaskInfo(creatTime,delay,run);
        TASK_INFO_LIST.add(taskInfo);
    }

    public static ScheduledFuture<?> schedule(Runnable runnable, Trigger trigger)
    {
        return THREAD_POOL_TASK_SCHEDULER.schedule(runnable, trigger);
    }

    static class MyThreadFactory implements ThreadFactory
    {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        MyThreadFactory(String prefix)
        {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            namePrefix = prefix + "-" +
                    poolNumber.getAndIncrement() +
                    "-thread-";
        }

        MyThreadFactory()
        {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            namePrefix = "xiaoxin-" +
                    poolNumber.getAndIncrement() +
                    "-thread-";
        }

        @Override
        public Thread newThread(Runnable r)
        {
            Thread t = new Thread(group, r,
                    namePrefix + threadNumber.getAndIncrement(),
                    0);
            if (t.isDaemon())
            {
                t.setDaemon(false);
            }
            if (t.getPriority() != Thread.NORM_PRIORITY)
            {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }

    static class TaskInfo
    {

        public TaskInfo() {}

        public TaskInfo(long creatTime, long delay, Runnable work)
        {
            this.creatTime = creatTime;
            this.delay = delay;
            this.work = work;
        }

        long creatTime;

        /**
         * 执行时间,单位:毫秒
         */
        long delay;

        Runnable work;

        public long getDelay() {
            return delay;
        }

        public void setDelay(long delay) {
            this.delay = delay;
        }

        public Runnable getWork() {
            return work;
        }

        public void setWork(Runnable work) {
            this.work = work;
        }

        public long getCreatTime() {
            return creatTime;
        }

        public void setCreatTime(long creatTime) {
            this.creatTime = creatTime;
        }
    }
}
